// Copyright (c) 2012-2013, yuno. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
//    this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of contributors
//    may be used to endorse or promote products derived from this software
//    without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
package sugarmintcandy.widgetwindow;

import java.util.ArrayList;
import java.util.LinkedHashSet;

import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.database.ContentObservable;
import android.database.ContentObserver;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Message;
import android.preference.PreferenceManager;
import android.util.DisplayMetrics;

public class MainDatabase implements Handler.Callback {
    private MainApplication mApplication;
    private SQLiteDatabase mDatabase;
    private ContentObservable mObservable;
    private Handler mHandler;

    public MainDatabase(MainApplication app) {
	mApplication = app;
	mObservable = new ContentObservable();
	mHandler = new Handler(this);
    }

    public Context getContext() {
	return mApplication;
    }

    public void close() {
	if (mDatabase != null) {
	    mDatabase.close();
	    mDatabase = null;
	}
    }

    private SQLiteDatabase getDatabase() {
	SQLiteDatabase db = mDatabase;
	if (db != null) {
	    return db;
	}
	db = mApplication.openOrCreateDatabase("main.db", Context.MODE_PRIVATE,
		null);
	int version = db.getVersion();
	if (version < 1) {
	    db.execSQL("create table t_window ("
		    + "c_app_widget_id integer primary key,"
		    + "c_window_x integer," + "c_window_y integer,"
		    + "c_window_width integer," + "c_window_height integer,"
		    + "c_window_color integer," + "c_title_bar_color integer,"
		    + "c_z_order integer)");
	    db.setVersion(1);
	}
	if (version < 2) {
	    db.execSQL("alter table t_window add column c_iconified integer");
	    db.execSQL("alter table t_window add column c_icon_x integer");
	    db.execSQL("alter table t_window add column c_icon_y integer");
	    db.execSQL("alter table t_window add column c_title_bar_text text");
	    db.execSQL("alter table t_window add column c_workspace integer");
	    ContentValues values = new ContentValues();
	    values.put("c_iconified", 0);
	    values.put("c_icon_x", Integer.MIN_VALUE);
	    values.put("c_icon_y", Integer.MIN_VALUE);
	    values.put("c_workspace", 1 << 1);
	    db.update("t_window", values, null, null);
	    db.execSQL("create table t_appwidgetid ("
		    + "_id integer primary key)");
	    db.setVersion(2);
	}
	if (version < 3) {
	    db.execSQL("create table t_recent_color ("
		    + "c_id integer primary key," + "c_color integer,"
		    + "c_last_used_time integer)");
	    if (version >= 1) {
		initRecentColorsFromWindowColors(db);
	    }
	    db.setVersion(3);
	}
	if (version < 4) {
	    if (version >= 1) {
		decreaseTitleBarHeight(db);
	    }
	    db.setVersion(4);
	}
	if (version < 5) {
	    db.execSQL("alter table t_window add column c_anchor text");
	    ContentValues values = new ContentValues();
	    values.put("c_anchor", Anchor.NONE.name());
	    db.update("t_window", values, null, null);
	}
	if (version < 6) {
	    db.execSQL("create table t_control_widget ("
		    + "c_id integer primary key," + "c_buttons integer)");
	    db.execSQL("alter table t_window add column c_host_id integer");
	    db.execSQL("alter table t_window add column c_title_bar_visible integer");
	    db.execSQL("alter table t_appwidgetid add column c_host_id integer");
	    ContentValues values = new ContentValues();
	    values.put("c_host_id", 1);
	    db.update("t_appwidgetid", values, null, null);
	    values.put("c_title_bar_visible", 1);
	    db.update("t_window", values, null, null);
	    db.setVersion(6);
	}
	return mDatabase = db;
    }

    private void initRecentColorsFromWindowColors(SQLiteDatabase db) {
	String[] columns = { "c_window_color", "c_title_bar_color" };
	String orderBy = "c_app_widget_id asc";
	Cursor cursor = db.query("t_window", columns, null, null, null, null,
		orderBy);
	if (cursor != null) {
	    int n = cursor.getCount();
	    if (n > 0) {
		LinkedHashSet<Integer> colors = new LinkedHashSet<Integer>();
		try {
		    for (int i = 0; i < n; i++) {
			cursor.moveToNext();
			colors.add(cursor.getInt(0) & 0xffffff);
			colors.add(cursor.getInt(1) & 0xffffff);
		    }
		} finally {
		    cursor.close();
		}
		ContentValues values = new ContentValues();
		values.put("c_last_used_time", System.currentTimeMillis());
		for (int color : colors) {
		    values.put("c_color", color);
		    db.insert("t_recent_color", null, values);
		}
	    }
	}
    }

    private void decreaseTitleBarHeight(SQLiteDatabase db) {
	int titleBarHeight = getContext().getResources().getDimensionPixelSize(
		R.dimen.default_title_bar_size);
	String table = "t_window";
	String[] columns = { "c_app_widget_id", "c_window_height" };
	Cursor cursor = db.query(table, columns, null, null, null, null, null);
	if (cursor != null) {
	    try {
		ContentValues values = new ContentValues();
		while (cursor.moveToNext()) {
		    long id = cursor.getLong(0);
		    int height = cursor.getInt(1);
		    values.put("c_window_height", height - titleBarHeight);
		    String where = "c_app_widget_id=" + id;
		    db.update(table, values, where, null);
		}
	    } finally {
		cursor.close();
	    }
	}
    }

    public WindowInfo getWindowInfo(int id) {
	SQLiteDatabase db = getDatabase();
	String table = "t_window";
	String where = "c_app_widget_id=" + id;
	Cursor c = db.query(table, null, where, null, null, null, null);
	if (c != null) {
	    try {
		while (c.moveToNext()) {
		    WindowInfo info = new WindowInfo();
		    info.load(mApplication, c);
		    return info;
		}
	    } finally {
		c.close();
	    }
	}
	return null;
    }

    public WindowInfo[] getWindowInfoList() {
	ArrayList<WindowInfo> list = new ArrayList<WindowInfo>();
	SQLiteDatabase db = getDatabase();
	String table = "t_window";
	String orderBy = "c_z_order ASC";
	Cursor c = db.query(table, null, null, null, null, null, orderBy);
	if (c != null) {
	    try {
		while (c.moveToNext()) {
		    WindowInfo info = new WindowInfo();
		    info.load(mApplication, c);
		    list.add(info);
		}
	    } finally {
		c.close();
	    }
	}
	return list.toArray(new WindowInfo[list.size()]);
    }

    public void addWindowInfo(WindowInfo info) {
	SQLiteDatabase db = getDatabase();
	String table = "t_window";
	ContentValues values = info.save();
	db.insert(table, null, values);
	notifyChange();
    }

    public void fetchWindowInfo(WindowInfo info) {
	SQLiteDatabase db = getDatabase();
	String table = "t_window";
	String where = "c_app_widget_id=" + info.appWidgetId;
	Cursor c = db.query(table, null, where, null, null, null, null);
	if (c != null) {
	    try {
		if (c.moveToNext()) {
		    info.load(mApplication, c);
		}
	    } finally {
		c.close();
	    }
	}
    }

    public void updateWindowInfo(WindowInfo info, boolean notifyChange) {
	SQLiteDatabase db = getDatabase();
	ContentValues values = info.save();
	String table = "t_window";
	String where = "c_app_widget_id=" + info.appWidgetId;
	db.update(table, values, where, null);
	if (notifyChange) {
	    notifyChange();
	}
    }

    public void deleteWindowInfo(WindowInfo info) {
	SQLiteDatabase db = getDatabase();
	String table = "t_window";
	String where = "c_app_widget_id=" + info.appWidgetId;
	db.delete(table, where, null);
	notifyChange();
    }

    public int getMaxZOrder() {
	SQLiteDatabase db = getDatabase();
	String table = "t_window";
	String[] columns = { "max(c_z_order)" };
	Cursor c = db.query(table, columns, null, null, null, null, null);
	if (c != null) {
	    try {
		if (c.moveToNext()) {
		    return c.getInt(0);
		}
	    } finally {
		c.close();
	    }
	}
	return 0;
    }

    public int getMaxHostId() {
	SQLiteDatabase db = getDatabase();
	String table = "t_window";
	String[] columns = { "max(c_host_id)" };
	Cursor c = db.query(table, columns, null, null, null, null, null);
	if (c != null) {
	    try {
		if (c.moveToNext()) {
		    return c.getInt(0);
		}
	    } finally {
		c.close();
	    }
	}
	return 0;
    }

    public int getNumberOfWindows() {
	SQLiteDatabase db = getDatabase();
	String table = "t_window";
	String[] selection = { "count(*)" };
	Cursor c = db.query(table, selection, null, null, null, null, null);
	if (c != null) {
	    try {
		if (c.moveToNext()) {
		    return c.getInt(0);
		}
	    } finally {
		c.close();
	    }
	}
	return 0;
    }

    public void addAppWidgetId(int id, int hostId) {
	SQLiteDatabase db = getDatabase();
	ContentValues values = new ContentValues();
	values.put("_id", id);
	values.put("c_host_id", hostId);
	db.insert("t_appwidgetid", null, values);
    }

    public void deleteAppWidgetId(int id) {
	SQLiteDatabase db = getDatabase();
	db.delete("t_appwidgetid", "_id=" + id, null);
    }

    public int getHostIdFromAppWidgetId(int id) {
	SQLiteDatabase db = getDatabase();
	String table = "t_appwidgetid";
	String[] columns = { "c_host_id" };
	String where = "_id=" + id;
	Cursor c = db.query(table, columns, where, null, null, null, null);
	if (c != null) {
	    try {
		if (c.moveToNext()) {
		    return c.getInt(0);
		}
	    } finally {
		c.close();
	    }
	}
	throw new IllegalArgumentException();
    }

    public int[] getAppWidgetIds() {
	int[] result = {};
	SQLiteDatabase db = getDatabase();
	String table = "t_appwidgetid";
	String[] columns = { "_id" };
	Cursor c = db.query(table, columns, null, null, null, null, null);
	if (c != null) {
	    try {
		int n = c.getCount();
		result = new int[n];
		for (int i = 0; i < n; i++) {
		    c.moveToNext();
		    result[i] = c.getInt(0);
		}
	    } finally {
		c.close();
	    }
	}
	return result;
    }

    public void registerObserver(ContentObserver observer) {
	mObservable.registerObserver(observer);
    }

    public void unregisterObserver(ContentObserver observer) {
	mObservable.unregisterObserver(observer);
    }

    public void notifyChange() {
	if (!mHandler.hasMessages(R.id.notify_change)) {
	    mHandler.sendEmptyMessage(R.id.notify_change);
	}
    }

    @SuppressWarnings("deprecation")
    @Override
    public boolean handleMessage(Message msg) {
	int what = msg.what;
	if (what == R.id.notify_change) {
	    mObservable.notifyChange(false);
	    fireUpdateWindows();
	}
	return true;
    }

    private void fireUpdateWindows() {
	Intent intent = new Intent(mApplication, MainService.class);
	intent.setAction(Constants.ACTION_UPDATE_WINDOWS);
	mApplication.startService(intent);
    }

    public PackageInfo getPackageInfo(int resId, int flags) throws Exception {
	String packageName = mApplication.getResources()
		.getResourcePackageName(resId);
	return mApplication.getPackageManager().getPackageInfo(packageName,
		flags);
    }

    public void addRecentColor(int color) {
	color &= 0xffffff;
	SQLiteDatabase db = getDatabase();
	ContentValues values = new ContentValues();
	values.put("c_last_used_time", System.currentTimeMillis());
	String table = "t_recent_color";
	String where = "c_color=" + color;
	if (db.update(table, values, where, null) == 0) {
	    values.put("c_color", color);
	    db.insert(table, null, values);
	}
    }

    public void deleteRecentColors() {
	SQLiteDatabase db = getDatabase();
	String table = "t_recent_color";
	db.delete(table, null, null);
    }

    public int[] getRecentColors() {
	int[] result = {};
	SQLiteDatabase db = getDatabase();
	String table = "t_recent_color";
	String[] columns = { "c_color" };
	String orderBy = "c_last_used_time desc, c_id desc";
	Cursor cursor = db.query(table, columns, null, null, null, null,
		orderBy);
	if (cursor != null) {
	    try {
		int n = cursor.getCount();
		result = new int[n];
		for (int i = 0; i < n; i++) {
		    cursor.moveToNext();
		    result[i] = cursor.getInt(0);
		}
	    } finally {
		cursor.close();
	    }
	}
	return result;
    }

    public void updateAnchor() {
	DisplayMetrics dm = Utilities.getDisplayMetrics(mApplication);
	SharedPreferences prefs = PreferenceManager
		.getDefaultSharedPreferences(mApplication);
	String titleBarSize = prefs.getString(PreferenceKeys.TITLE_BAR_SIZE,
		null);
	int titleBarHeight = Math.round(dm.density
		* Integer.parseInt(titleBarSize));
	int iconSize = mApplication.getResources().getDimensionPixelSize(
		R.dimen.icon_size);
	Anchor anchor = Anchor.valueOf(prefs.getString(PreferenceKeys.ANCHOR,
		null));
	WindowInfo[] infoList = getWindowInfoList();
	for (WindowInfo info : infoList) {
	    if (info.anchor != anchor) {
		Rect r = info.anchor.decodeRect(dm, info.windowX, info.windowY,
			info.windowWidth, info.windowHeight + titleBarHeight);
		Point p = anchor.encodePoint(dm, r.left, r.top, r.right,
			r.bottom);
		info.windowX = p.x;
		info.windowY = p.y;
		r = info.anchor.decodeRect(dm, info.iconX, info.iconY,
			iconSize, iconSize);
		p = anchor.encodePoint(dm, r.left, r.top, r.right, r.bottom);
		info.iconX = p.x;
		info.iconY = p.y;
		info.anchor = anchor;
		updateWindowInfo(info, false);
	    }
	}
	notifyChange();
    }

    public int getControlWidgetButtons(int id) {
	SQLiteDatabase db = getDatabase();
	String table = "t_control_widget";
	String[] columns = { "c_buttons" };
	String where = "c_id=" + id;
	Cursor c = db.query(table, columns, where, null, null, null, null);
	if (c != null) {
	    try {
		if (c.moveToNext()) {
		    return c.getInt(0);
		}
	    } finally {
		c.close();
	    }
	}
	return 31;
    }

    public void setControlWidgetButtons(int id, int buttons) {
	SQLiteDatabase db = getDatabase();
	ContentValues values = new ContentValues();
	values.put("c_buttons", buttons);
	String table = "t_control_widget";
	String where = "c_id=" + id;
	if (db.update(table, values, where, null) == 0) {
	    values.put("c_id", id);
	    db.insert(table, null, values);
	}
    }

    public void deleteControlWidget(int id) {
	SQLiteDatabase db = getDatabase();
	db.delete("t_control_widget", "c_id=" + id, null);
    }
}
